﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace IWMAG.Core.EnhancedProperties.PropertyNodes
{
    internal partial class PropertyNodeList<T> : PropertyNodeList, IObservable<T>
    {
        private StartNode<T> StartOfList { get; set; }
        
        private UniqueIdentifier associatedObjectId;
        private PropertyBacker<T> associatedBacker;

        private Func<T, T> coerceValueCallback;

        public PropertyNodeList(DefaultValueNode<T> defaultNode, Func<T,T> coerceValue, UniqueIdentifier asocId, PropertyBacker<T> asocBacker)
        {
            Contract.Requires(defaultNode != null);
            
            StartNode<T> start = new StartNode<T>((k) => RaisePropertyChanged(k), () => ListModified());
            start.Next = defaultNode;
            this.coerceValueCallback = coerceValue;
            StartOfList = start;
            this.associatedObjectId = asocId;
            this.associatedBacker = asocBacker;
            this.observers = new List<IObserver<T>>();
        }

        private void ListModified()
        {
            if (!associatedBacker.Metadata.IsFrequentAccess && this.IsEmptyList())
            {
                associatedBacker.PropertyNodeListRequestsDeletion(associatedObjectId);
            }
            
        }

        public void AddOrModifyListMember<K>(int priority, Func<int, K> replaceNode, Func<K, bool> modifyNode)
            where K : PropertyNode<T>
        {
           Contract.Requires(typeof(K) != typeof(DefaultValueNode<T>));
           Contract.Requires(modifyNode != null);
           Contract.Requires(replaceNode != null);
           Contract.Requires(Math.Abs(priority) < 100000);
            var prev = StartOfList as PropertyNode<T>;
            var cur = StartOfList.Next;
            while ((cur as DefaultValueNode<T>) == null)
            {
                if (cur.Priority == priority)
                {
                    cur = FoundNodeToModifyOrReplace<K>(priority, replaceNode, modifyNode, cur);

                    return;
                }
                else if (cur.Priority < priority)
                {
                    MovedPastTargetPriorityInsertBefore<K>(priority, replaceNode, cur);
                    return;
                }
                else
                {
                    cur = cur.Next;
                    prev = prev.Next;
                }
            }
            FellOfEndOfListInsertAtEnd<K>(priority, replaceNode, prev, cur);
        }

        private static void FellOfEndOfListInsertAtEnd<K>(int priority, Func<int, K> replaceNode, PropertyNode<T> prev, PropertyNode<T> cur) where K : PropertyNode<T>
        {
            var insertNode = replaceNode(priority);
            if (insertNode.Priority != priority)
            {
                throw new ArgumentException();
            }

            prev.Next = insertNode;
            insertNode.Previous = prev;
            insertNode.Next = cur;
            var getkicker = insertNode.GetNodeValue();
            prev.SuccessorChanged(getkicker);
        }

        private static void MovedPastTargetPriorityInsertBefore<K>(int priority, Func<int, K> replaceNode, PropertyNode<T> cur) where K : PropertyNode<T>
        {
            var newNode = replaceNode(priority);
            if (newNode.Priority != priority)
            {
                throw new ArgumentException();
            }

            cur.Previous.Next = newNode;
            newNode.Previous = cur.Previous;
            cur.Previous = newNode;
            newNode.Next = cur;

            var kicker = newNode.GetNodeValue();
            newNode.Previous.SuccessorChanged(kicker);
        }

        private static PropertyNode<T> FoundNodeToModifyOrReplace<K>(int priority, Func<int, K> replaceNode, Func<K, bool> modifyNode, PropertyNode<T> cur) where K : PropertyNode<T>
        {
            var casted = cur as K;
            bool modifySuccessful = false;
            if (casted != null)
            {
                modifySuccessful = modifyNode(casted);
            }

            if (!modifySuccessful)
            {
                var getRep = replaceNode(priority);
                if (getRep.Priority != priority)
                {
                    throw new ArgumentException();
                }

                getRep.Previous = cur.Previous;
                getRep.Next = cur.Next;
                cur.Next.Previous = getRep;
                cur.Previous.Next = getRep;
                cur.Dispose();
                cur = null;
                var kicker = getRep.GetNodeValue();
                getRep.Previous.SuccessorChanged(kicker);
            }
            return cur;
        }

        public bool RemoveListMember(int priority)
        {
            var prev = StartOfList as PropertyNode<T>;
            var cur = prev.Next;
            while ((cur as DefaultValueNode<T>) == null)
            {
                if (cur.Priority==priority)
                {
                    prev.Next = cur.Next;
                    cur.Next.Previous = prev;
                    cur.Dispose();
                    cur = null;
                    prev.SuccessorChanged(prev.Next.GetNodeValue());
                    return true;
                }
                else if (cur.Priority < priority)
                {
                    return false;
                }

                prev = cur;
                cur = cur.Next;
            }

            return false;
        }

        private void RaisePropertyChanged(T k)
        {
            foreach (var item in observers)
            {
                item.OnNext(k);

            }
        }

        public bool IsEmptyList()
        {
            if (observers.Count > 0)
            {
                return false;
            }

            if (StartOfList.Next as DefaultValueNode<T> == null)
            {
                return false;
            }
            
            return true;
        }


        public override void Dispose()
        {
            StartOfList.PropertyChangedDelegate = null;
            PropertyNode<T> cur = StartOfList.Next;
           
            while ((cur as DefaultValueNode<T>) == null)
            {
                var tmp = cur.Next;
                cur.Dispose();
                cur = tmp;
            }
            StartOfList.Dispose();

            foreach (var item in observers)
            {
                item.OnCompleted();
            }

            observers.Clear();
            observers = null;

            this.coerceValueCallback = null;
        }

        internal T GetValue()
        {
            return StartOfList.GetNodeValue();
        }

        internal int GetHighestPriority()
        {
            return Math.Max(-1, StartOfList.Next.Priority);
        }

        public IDisposable Subscribe(IObserver<T> observer)
        {
            observers.Add(observer);
            observer.OnNext(GetValue());
            return new PropertyNodeListObserver<T>(this, observer);
        }

        private void Unsubscribe(IObserver<T> observer)
        {
            observers.Remove(observer);
        }
        internal class PropertyNodeListObserver<K> : IDisposable
        {
            PropertyNodeList<K> list;
            IObserver<K> observer;
            public PropertyNodeListObserver(PropertyNodeList<K> list, IObserver<K> observer)
            {
                this.list = list;
                this.observer = observer;
            }
            public void Dispose()
            {
                list.Unsubscribe(observer);
            }
        }

        List<IObserver<T>> observers;
    }


    internal abstract class PropertyNodeList : IDisposable
    {
        public abstract void Dispose();
    }
}
